﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net.Http;

namespace NFinal.Middleware.Proxy
{
    public delegate Task InvokeDelegate<TContext>(TContext context);
    /// <summary>
    /// Owin代理中间件
    /// </summary>
    public class ProxyMiddleware<TContext>
    {
        private readonly InvokeDelegate<TContext> next;
        private ProxyConfig config;
        public ProxyMiddleware(InvokeDelegate<TContext> next, ProxyConfig config)
        {
            this.config = config;
            this.next = next;
        }
        public async Task Invoke(TContext context)
        {
            try
            {
                var request = CreateRequestMessage(context);
                await ExecuteRequest(request, context);
            }
            catch (Exception e)
            {
                await next(context);
            }
        }
        public virtual string GetRequestMethod(TContext context)
        {
            throw new NotImplementedException();
        }
        public virtual string GetRequestPath(TContext context)
        {
            throw new NotImplementedException();
        }
        public virtual string GetRequestQueryString(TContext context)
        {
            throw new NotImplementedException();
        }
        public virtual void AddOrUpdateResponseHeader(TContext context,string key,string[] value)
        {
            throw new NotImplementedException();
        }
        public virtual void RemoveResponseHeader(TContext context, string key)
        {
            throw new NotImplementedException();
        }
        public virtual void AddRequestHeadersToHttpRequestMessage(TContext context,HttpRequestMessage message)
        {
            throw new NotImplementedException();
        }
        public virtual CancellationToken GetCancellationToken(TContext context)
        {
            throw new NotImplementedException();
        }
        public virtual void SetResponseStatusCode(TContext context, int statusCode)
        {
            throw new NotImplementedException();
        }
        public void AddRequestHeaderToHttpRequestMessage(TContext context, HttpRequestMessage message,string key,string[] value)
        {
            if (IgnoreHeaders.Contains(key))
            {
                return;
            }
            if (!message.Headers.TryAddWithoutValidation(key, value.ToArray()))
                message.Content?.Headers.TryAddWithoutValidation(key, value.ToArray());
        }
        public virtual System.IO.Stream GetRequestBody(TContext context)
        {
            throw new NotImplementedException();
        }
        public virtual System.IO.Stream GetResponseBody(TContext context)
        {
            throw new NotImplementedException();
        }
        private static readonly IEnumerable<string> IgnoreHeaders = new[]
        {
            "Authorization",
            "Host"
        };
        private HttpRequestMessage CreateRequestMessage(TContext context)
        {
            string requestPath = GetRequestPath(context);
            string url = null;
            Proxy proxy = null;
            for (int i = 0; i < config.Proxs.Length; i++)
            {
                if (requestPath.StartsWith(config.Proxs[i].urlPrefix))
                {
                    proxy = config.Proxs[i];
                    
                    if (requestPath.Length == proxy.urlPrefix.Length)
                    {
                        url = "/";
                    }
                    else
                    {
                        url = requestPath.Substring(config.Proxs[i].urlPrefix.Length);
                    }
                    url = proxy.url+requestPath.Substring(config.Proxs[i].urlPrefix.Length);
                    string queryString = GetRequestQueryString(context);
                    if (queryString != string.Empty)
                    {
                         url+= "?" + queryString;
                    }
                    break;
                }
            }
            if (proxy != null)
            {
                var message = new HttpRequestMessage
                {
                    Method = new HttpMethod(GetRequestMethod(context)),
                    RequestUri = new Uri(url)
                };
                AddRequestHeadersToHttpRequestMessage(context,message);
                if (HasBody(GetRequestMethod(context)))
                {
                    message.Content = new StreamContent(GetRequestBody(context));
                }
                return message;
            }
            else
            {
                return null;
            }
        }
        private static bool HasBody(string method)
        {
            return !new[] { "GET", "HEAD", "DELETE", "TRACE" }
                .Any(m => string.Equals(method, m, StringComparison.OrdinalIgnoreCase));
        }

        private async Task ExecuteRequest(HttpRequestMessage message, TContext context)
        {
            HttpResponseMessage response=null;
            try
            {
                response = await GetResponse(message,GetCancellationToken(context));

                //var content =await response.Content.ReadAsStreamAsync();
                SetResponseStatusCode(context, (int)response.StatusCode);

                foreach (var header in response.Headers)
                    AddOrUpdateResponseHeader(context,header.Key, header.Value.ToArray());

                foreach (var header in response.Content.Headers)
                    AddOrUpdateResponseHeader(context, header.Key, header.Value.ToArray());

                RemoveResponseHeader(context,"transfer-encoding");
                System.IO.Stream stream = GetResponseBody(context);
                await response.Content.CopyToAsync(stream);
            }
            catch (Exception ex)
            {
                SetResponseStatusCode(context, 403);
            }
            finally
            {
                response?.Dispose();
            }
        }
        private static async Task<HttpResponseMessage> GetResponse(
    HttpRequestMessage message, CancellationToken cancellation)
        {
            using (var client = new HttpClient(new HttpClientHandler()))
            {
                return await client.SendAsync(message,cancellation);
            }
        }
    }
}
